/*
 * Copyright (C) 2010 ST-Ericsson AS
 * Author: Erwan Bracq / erwan.bracq@stericsson.com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 */

#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <fcntl.h>
#include <linux/reboot.h>
#include <sys/reboot.h>

#include "mid.h"
#include "mid_log.h"
#include "mid_error.h"
#include "mid_power.h"
#include "mid_statemachine.h"
#include "mid_sys.h"

/* NOTE: This define section should match the helper driver opcode */
#define EN_GPIOSERV_SAFE_MODE	"1\n"
#define DIS_GPIOSERV_SAFE_MODE	"2\n"
#define EN_GPIOUT_SAFE_MODE	"3\n"
#define DIS_GPIOUT_SAFE_MODE	"4\n"
#define EN_IOS_SAFE_MODE	"5\n"
#define DIS_IOS_SAFE_MODE	"6\n"
#define PLATFORM_REBOOT		"7\n"
#define EN_TX_SAFE_MODE	"11\n"
#define DIS_TX_SAFE_MODE	"12\n"

#ifdef HAVE_ANDROID_OS

#define WAKELOCK_NAME	"mid"
#define WAKELOCK	"/sys/power/wake_lock"
#define WAKEUNLOCK	"/sys/power/wake_unlock"
#endif
#define LATENCY		"/sys/power/cpu_latency"
#define SAFECONTROL	"/sys/class/mid_safecontrol/exec_opcode"

struct mid_power
{
	int latency_fd;
	int wakelock_fd;
	int wakeunlock_fd;
	int mid_safecontrol_fd;
};

struct mid_power* mid_power_init(void)
{
	struct mid_power* context = malloc(sizeof(struct mid_power));
	if (context == NULL) return NULL;

	context->latency_fd = -1;
	context->wakelock_fd = -1;
	context->wakeunlock_fd = -1;
	context->mid_safecontrol_fd = -1;

	context->latency_fd = open(LATENCY, O_WRONLY);
	if (context->latency_fd == -1) {
		MLGW("POW: Failed to open CPU latency entry %s. "
			"Error %s - Not supported into this BSP?",
			LATENCY, strerror(errno));
	// Not fatal. Just continue.
	}
#ifdef HAVE_ANDROID_OS
	context->wakelock_fd = open(WAKELOCK, O_WRONLY);
	if (context->wakelock_fd == -1) {
		MLGW("POW: Failed to open wakelock " WAKELOCK ". Error %s - Not supported into this BSP?", strerror(errno));
		// Not fatal. Just continue.
	}
	context->wakeunlock_fd = open(WAKEUNLOCK, O_WRONLY);
	if (context->wakeunlock_fd == -1) {
		MLGW("POW: Failed to open wakeunlock " WAKEUNLOCK ". Error %s - Not supported into this BSP?", strerror(errno));
		// Not fatal. Just continue.
	}
#endif
	context->mid_safecontrol_fd = open(SAFECONTROL, O_WRONLY);
	if (context->mid_safecontrol_fd == -1) {
		MLGW("POW: Failed to open MID safecontrol entry " SAFECONTROL
			". Error %s - Not supported into this BSP?",
			strerror(errno));
		// Not fatal. Just continue.
	}

	return context;
}

#ifdef HAVE_ANDROID_OS
static bool android_set_wakelock(struct mid_power* context, char *name) {
	if (context->wakelock_fd == -1) {
		MLGE("POW: Failed to set wakelock %s, since opening the wakelock file failed at startup", name);
		return false;
	}
	if (write(context->wakelock_fd, name, strlen(name)) == -1) {
		MLGE("POW: Failed to write wakelock value: %s",
			strerror(errno));
		return false;
	}
	return true;
}

static bool android_set_wakeunlock(struct mid_power* context, char *name) {
	if (context->wakeunlock_fd == -1) {
		MLGE("POW: Failed to release wakelock %s, since opening the wakeunlock file failed at startup", name);
		return false;
	}
	if (write(context->wakeunlock_fd, name, strlen(name)) == -1) {
		MLGE("POW: Failed to write wakeunlock value: %s",
			strerror(errno));
		return false;
	}
	return true;
}
#endif

/* To prevent calling this twice without calling prevent in between */
#ifdef HAVE_ANDROID_OS
static int wakelocked = 0;
#endif
#ifdef HAVE_ANDROID_OS
int mid_set_wakelock(struct mid_power* context, char *name, int set)
{
	int res;
	if (set)
		res = android_set_wakelock(context, name);
	else
		res = android_set_wakeunlock(context, name);
	return res?0:-1;
}
#endif /* HAVE_ANDROID_OS */

bool mid_permit_suspend(struct mid_power* context) {
	bool res = true;
#ifdef HAVE_ANDROID_OS
	if (wakelocked)
	{
		MLGD("POW: Request WAKEUNLOCK");
		res = android_set_wakeunlock(context, WAKELOCK_NAME);
		wakelocked = 0;
	}
#endif
	return res;
}

bool mid_prevent_suspend(struct mid_power* context) {
	bool res = true;
#ifdef HAVE_ANDROID_OS
	if (!wakelocked)
		{
		MLGD("POW: Request WAKELOCK");
		res = android_set_wakelock(context, WAKELOCK_NAME);
		wakelocked = 1;
	}
#endif
	return res;
}

void mid_enable_tx_safe_mode(struct mid *midinfo) {
	if (midinfo->mid_power->mid_safecontrol_fd == -1)
		MLGE("POW: Failed to enable TX safe mode, since opening the mid_safecontrol sysfs entry failed at startup");
	if (write(midinfo->mid_power->mid_safecontrol_fd, EN_IOS_SAFE_MODE, sizeof(EN_TX_SAFE_MODE)) == -1) {
		MLGE("MID: Failed to write to mid_safecontrol sysfs: %s.",
				strerror(errno));
	}
}

void mid_disable_tx_safe_mode(struct mid *midinfo) {
	if (midinfo->mid_power->mid_safecontrol_fd == -1)
		MLGE("POW: Failed to disable TX safe mode, since opening the mid_safecontrol sysfs entry failed at startup");
#if defined(MOTOROLA_FEATURE)
	if (write(midinfo->mid_power->mid_safecontrol_fd, DIS_IOS_SAFE_MODE, sizeof(DIS_TX_SAFE_MODE)) == -1) {
#else
	if (write(midinfo->mid_power->mid_safecontrol_fd, EN_IOS_SAFE_MODE, sizeof(DIS_TX_SAFE_MODE)) == -1) {
#endif
		MLGE("MID: Failed to write to mid_safecontrol sysfs: %s.",
				strerror(errno));
	}
}

void mid_enable_io_safe_mode(struct mid *midinfo) {
	if (midinfo->mid_power->mid_safecontrol_fd == -1)
		MLGE("POW: Failed to set IOs in safe mode, since opening the mid_safecontrol sysfs entry failed at startup");
	if (write(midinfo->mid_power->mid_safecontrol_fd, EN_IOS_SAFE_MODE, sizeof(EN_IOS_SAFE_MODE)) == -1) {
		MLGE("MID: Failed to write to mid_safecontrol sysfs: %s.",
				strerror(errno));
	}
}

void mid_disable_io_safe_mode(struct mid *midinfo) {
	if (midinfo->mid_power->mid_safecontrol_fd == -1) {
		MLGE("POW: Failed to disable IOs safe mode, since opening the mid_safecontrol sysfs entry failed at startup");
	}
	if (write(midinfo->mid_power->mid_safecontrol_fd, DIS_IOS_SAFE_MODE, sizeof(DIS_IOS_SAFE_MODE)) == -1) {
		MLGE("MID: Failed to write to mid_safecontrol sysfs: %s.",
				strerror(errno));
	}
}

void mid_enable_gpio_service_safe_mode(struct mid *midinfo) {
	if (midinfo->mid_power->mid_safecontrol_fd == -1) {
		MLGE("POW: Failed to set GPIO SERVICE in safe mode, since opening the mid_safecontrol sysfs entry failed at startup");
	}
	if (write(midinfo->mid_power->mid_safecontrol_fd, EN_GPIOSERV_SAFE_MODE, sizeof(EN_GPIOSERV_SAFE_MODE)) == -1) {
		MLGE("MID: Failed to write to mid_safecontrol sysfs: %s.",
				strerror(errno));
	}
}

void mid_enable_gpio_out_safe_mode(struct mid *midinfo) {
	if (midinfo->mid_power->mid_safecontrol_fd == -1) {
		MLGE("POW: Failed to set GPIO OUT in safe mode, since opening the mid_safecontrol sysfs entry failed at startup");
	}
	if (write(midinfo->mid_power->mid_safecontrol_fd, EN_GPIOUT_SAFE_MODE, sizeof(EN_GPIOUT_SAFE_MODE)) == -1) {
		MLGE("MID: Failed to write to mid_safecontrol sysfs: %s.",
				strerror(errno));
	}
}

void mid_disable_gpio_service_safe_mode(struct mid *midinfo) {
	if (midinfo->mid_power->mid_safecontrol_fd == -1) {
		MLGE("POW: Failed to disable GPIO SERVICE safe mode, since opening the mid_safecontrol sysfs entry failed at startup");
	}
	if (write(midinfo->mid_power->mid_safecontrol_fd, DIS_GPIOSERV_SAFE_MODE, sizeof(DIS_GPIOSERV_SAFE_MODE)) == -1) {
		MLGE("MID: Failed to write to mid_safecontrol sysfs: %s.",
				strerror(errno));
	}
}

void mid_disable_gpio_out_safe_mode(struct mid *midinfo) {
	if (midinfo->mid_power->mid_safecontrol_fd == -1) {
		MLGE("POW: Failed to disable GPIO OUT safe mode, since opening the mid_safecontrol sysfs entry failed at startup");
	}
	if (write(midinfo->mid_power->mid_safecontrol_fd, DIS_GPIOUT_SAFE_MODE, sizeof(DIS_GPIOUT_SAFE_MODE)) == -1) {
		MLGE("MID: Failed to write to mid_safecontrol sysfs: %s.",
				strerror(errno));
	}
}

void mid_platform_reboot(struct mid *midinfo){
	int res = 0;
	/* If reboot_on_failure not set we cowardly refuse to reboot */
	if (!(midinfo->config->reboot_on_failure)) {
		MLGW("MID: reboot_on_failure option not set. MID cowardly refuse to reboot the platform and will spin.");
		goto spinloop;
	}
	/* Flush the filesystem. */
	sync();
	/* If a register is specified, we use it.
	 * It s a CPU HW resest. */
	if (midinfo->config->use_cpu_reset_reg) {
		if (midinfo->mid_power->mid_safecontrol_fd == -1) {
			MLGE("POW: Failed to write to platform RESET register, since opening the mid_safecontrol sysfs entry failed at startup.");
			MLGE("POW: Failure to reset the platform. MID will spin forever.");
		} else
		if (write(midinfo->mid_power->mid_safecontrol_fd, PLATFORM_REBOOT, sizeof(PLATFORM_REBOOT)) == -1) {
			res = errno;
			MLGE("MID: Failed to write to mid_safecontrol sysfs: %s.",
				strerror(res));
		}
	}
	/* Else, we use standard reboot system call. */
	else {
#ifdef HAVE_ANDROID_OS
		MLGD("POW: System reboot. Reset reason: %s", midinfo->config->cpu_reset_reason);
		__reboot(LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2,
				LINUX_REBOOT_CMD_RESTART2,
				midinfo->config->cpu_reset_reason);
#else
		MLGD("POW: System reboot is call.");
		reboot(RB_AUTOBOOT);
#endif
	}
	/* Wait platform reboot */
	/* Everything is going mad. So filesystem is flushed, we
	 * terminate and don t care to exit properly. Reboot is nearby. */
spinloop:
	MLGD("MID: MID entered in a final spinning loop. Due to unrecoverable condition. Restart your platform.");
	while(1)
		sleep(1);
}
